package container

import (
	"github.com/sirupsen/logrus"
	"math/rand"
	"os"
	"os/exec"
	"path"
	"strings"
	"syscall"
	"time"
	"ycontainer/cgroups"
	"ycontainer/compress"
	"ycontainer/network"
	"ycontainer/unionfs"
)

type Container struct {
	Id         string `json:"id,omitempty"`
	Pid        int    `json:"pid,omitempty"`
	Status     string `json:"status,omitempty"`
	CreateTime string `json:"createTime,omitempty"`

	*CreateParams
}

type CreateParams struct {
	Name            string
	ImageName       string
	Detach          bool
	StartCmdAndArgs []string
	Volumes         []string
	ResourceLimit   *cgroups.ResourceLimit
	Network         string
}

const (
	Ycontainer     = "ycontainer"
	YcontainerRoot = "/var/lib/" + Ycontainer
	running        = "running"
	stopped        = "stopped"
	exited         = "exited"
)

func (c *Container) Create() error {
	cmd, w := createParentProcess(c)

	rootfs := path.Join(YcontainerRoot, "overlay", c.Name)

	cmd.Dir = unionfs.Manager.Mount(rootfs, c.Volumes)
	if err := compress.Unpack(c.ImageName, unionfs.Manager.Merged); err != nil {
		return err
	}

	if err := cmd.Start(); err != nil {
		logrus.Errorf("err: %v", err)
		return err
	}

	c.rich(cmd)

	c.limit()
	c.sendMessage(w)

	if c.Network != "" {
		c.connectNetwork()
	}

	c.Record()

	if c.Detach {
		return nil
	}
	cmd.Wait()

	unionfs.Manager.Umount(rootfs, c.Volumes)
	c.RemoveRecord()
	os.Exit(-1)
	return nil
}

func (c *Container) rich(cmd *exec.Cmd) {
	c.Pid = cmd.Process.Pid
	c.Id = idGenerator(12)
	c.Status = running
	c.CreateTime = time.Now().Format(time.RFC3339)
}

func (c *Container) sendMessage(w *os.File) {
	w.WriteString(strings.Join(c.StartCmdAndArgs, " "))
	w.Close()
}

func (c *Container) limit() {
	cgroupManager := cgroups.NewCgroupManager(Ycontainer)
	defer cgroupManager.Destroy()

	cgroupManager.Set(c.ResourceLimit)
	cgroupManager.Apply(c.Pid)
}

func (c *Container) connectNetwork() {
	network.InitDriverAndNetwork()

	network.Connect(c.Network, c.Id, c.Pid)
}

func createParentProcess(c *Container) (*exec.Cmd, *os.File) {
	cmd := exec.Command("/proc/self/exe", "init")

	// CentOS 7 默认关闭了user namespaces，所以在添加上CLONE_NEWUSER会报`invalid argument`错误
	// 临时开启（重启失效）：echo 1 > /proc/sys/user/max_user_namespaces
	// 永久开启（重启生效）：echo "user.max_user_namespaces=1" >> /etc/sysctl.conf
	cmd.SysProcAttr = &syscall.SysProcAttr{
		Cloneflags: syscall.CLONE_NEWUTS | syscall.CLONE_NEWPID | syscall.CLONE_NEWNS |
			syscall.CLONE_NEWNET | syscall.CLONE_NEWIPC,
	}

	if c.Detach {
		cmd.Stdout = CreateStdOutLogFile(c)
	} else {
		cmd.Stdin = os.Stdin
		cmd.Stdout = os.Stdout
		cmd.Stderr = os.Stderr
	}

	// 用来给init传递参数
	// 1个进程默认会有3个文件描述符，分别是标准输入、标准输出、标准错误
	// 再加一个额外的第四个用来给子进程传递参数
	r, w, err := os.Pipe()
	if err != nil {
		logrus.Errorf("new pipe err: %v", err)
	}

	cmd.ExtraFiles = []*os.File{r}
	return cmd, w
}

func idGenerator(n int) string {
	letterBytes := "1234567890abcdefjhijklmnopqrstuvwxyz"
	rand.Seed(time.Now().UnixNano())
	b := make([]byte, n)
	for i := range b {
		b[i] = letterBytes[rand.Intn(len(letterBytes))]
	}
	return string(b)
}
